什么玩意儿?| 网站新增暗夜模式,AI搓出来的代码分享

耳朵的主人²º²⁵

2025-10-28

429人阅读

东西坏了,靠我修,其实我也很不喜欢修东西,只是我还不想扔了它,所以我坚持坏了就修,这就显得我很主动,东西就觉得我离不开它了,它就可以一直坏一直坏,然后我一直修一直修。有没有一种可能?有一天,我不想修了呢?或者它不配了呢?我不要它了,那它是个什么玩意儿?

— 源于2025-10-09更新的一段内容。

那天我挺憋屈的,缘由说来也很搞笑,鸡毛蒜皮的一丁点小事,就这样燃起来的家庭纷争,我自身也是很诧异,为什么就这么点小事我压不住自己的怒火了,可能因为情绪总有一个临界点,我一直是忍让型的追求家和万事兴的态度,但是当下,有一个认知突然占据了我的思想,凭什么赚钱养家的是我,忍气吞声的还是我,所以我就炸了。

我本不想吵架,因为吵赢了只是把矛盾推向更高的地方,无法调和。所以大晚上的我就到操场上快走慢跑了8公里发泄自己的情绪,跑着跑着,越跑越不对劲,情绪更沮丧了,累瘫了坐在十字路口的小石墩上看着红绿灯变换读秒,挺落寞的,也挺心疼自己。

末了,我跟自己说:算了,都是自己惯的,能过就过,不能过就算了,这次我绝不先认怂。

闹矛盾之前,有听她念叨说 iPhone 17 Pro Max 京东本地都没货,所以我记在心里了,贝勒爷的同学家长有位是卖apple周边设备的,所以就跟他讲有货跟我说一声,找他拿一台。

闹矛盾之后,卖手机的说是手机有货了,溢价450元,问我要什么颜色,这都吵架冷战了,我也不好认怂去问她喜欢什么颜色啊,本来想问的,但是,呃,先开口不就输了吗?好,我不问。

不知道她喜欢什么颜色,就拿橙色和银色两台512GB回家,丢在桌子上。这次我对自己大方起来了,以往都是给她追新款,然后我自己随意将就,两台手机13和14用了好多年了,我也没舍得给自己换。心想让她先挑,剩下的我自己用,我也要开始自己享受消费了,这种心理有点变态,呃,为啥我赚钱给她优质生活,然后我自己对自己这么抠,然后她还可以这么高高在上的理所应当的不照顾我的感受。

手机在餐边柜桌子上放了两天,她也没动,我也没动,其实她动了,就是没拆封,因为装在袋子里的叠放顺序变了,肯定是我不在家的时候,自己拿起来纠结了半天,到底拆不拆呢,拆了不就认输了吗?

第三天早上,我还在睡觉,她到房间来,叫醒我,要我给她贴贴膜,拿着橙色和银色比划了半天,纠结要选什么颜色,我说你随便,你选好一台,另一台给我留着,然后我继续睡觉,十一点多起床后,她说要不她拿橙色吧,好的,那我就用修炼了十几年的贴膜技术完美的给她贴好了,但是这时候我还是端着呢,我话不多说,贴完橙色贴银色,贴好后,她拿走银色对比了一下,说银色比较商务,还是你拿吧。

一切好像从未发生过,呃,吵架,呃,根本没有发生过。晚上回房的时候,主动贴贴了,假装无意的翻个身靠着我,在试探我是不是不生气了,其实我早不生气了,真没骨气,唉。我内心其实很清楚,她的性格都是这些年我给她娇惯出来的,我当面指责她恃宠而骄,她并不否认。

但是日子总要过下去的吧,其实她还不错,就是这个家传的性格,有些太蛮横不讲理,事事都要顺遂她的心意,逆反她就是跟她势不两立。但是也相对收敛了很多,可能她也采集到我不忍了的想法,所以不敢再危险边缘来回试探蹦跶了。

果然,修东西,我还是一如既往的专业。


叨叨完,开始下一个主题,关于网站新增暗夜模式。

之前有博友评论说网站没有暗夜模式,可能体验感不佳吧,我现在对于修改网站主题有相当大的心理阴影,因为我的主题都是仿的,看到这个好看我就扒了,然后套进去自己的主题里,导致主题代码垒得跟屎山一样,一个主题里有七八个CSS样式文件,和一大堆可能无用可能有用的js文件,我主张,能用就行。但是这就给修改增加了很大的难度,有时候改一点点小东西,完犊子,前端垮了,垮在哪里,我也不知道,我再找找,就很难。

前几天逛 Jeffer.Z 的新作站点 FindBlog 觉得他的暗夜模式配色挺好看的,嗯,蠢蠢欲动的参照他站点的配色开始折腾了,他说站点黑夜模式是一个第三方的js库,直接调用就行。/npm/[email protected]/darkreader.js

听不懂,一点都听不懂,想到用插件来实现,因为我主题魔改的东西太多,所以导致插件装上去后,各种惨不忍睹的前端画面出现了,算了,我用我最简单暴力的方式来折腾吧。

我的想法是这样,我创建两个不同的配色方案样式文件,一个style-dark.css,一个style-light.css,然后通过js判断来识别加载哪一个文件,按道理说这个应该就可以满足我的需求,我打开了Gemini对接的聊天框,https://chat.erduo.tech,搭建来自己用的,因为对比之下,元宝的Deepseek和混元产出的代码内容,总是有各种问题出现,当然,可能是我站着尿尿的姿势不对,也可能是对准的方向不对,总之,我还是用的Gemini 2.5 Flash模型解决。

最早生成的插件php代码如下:

<?php
/**
 * Plugin Name: 日夜模式切换器
 * Plugin URI: https://www.edzbe.com
 * Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
 * Version: 1.0.1
 * Author: 耳朵的主人
 * Author URI: https://www.edzbe.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: daynight-switcher
 * Domain Path: /languages
 */

// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * 注册并加载日夜模式样式和脚本
 */

function dns_enqueue_styles_scripts() {
    // 获取主题目录URI(确保CSS文件位于主题的/css/子目录)
    $theme_css_dir_uri = get_template_directory_uri() . '/css/';

    // 注册白天模式样式(WordPress会自动生成ID:daynight-switcher-light-style-css)
    wp_enqueue_style(
        'daynight-switcher-light-style',
        $theme_css_dir_uri . 'style-light.css',
        array(),
        '1.0.0'
    );

    // 注册暗夜模式样式(WordPress会自动生成ID:daynight-switcher-dark-style-css)
    wp_enqueue_style(
        'daynight-switcher-dark-style',
        $theme_css_dir_uri . 'style-dark.css',
        array(),
        '1.0.0'
    );

    // 注册切换脚本(在页脚加载,确保DOM就绪)
    wp_enqueue_script(
        'daynight-switcher-script',
        plugins_url( 'js/daynight-switcher.js', __FILE__ ),
        array(),
        '1.0.0',
        true
    );

    // 将PHP数据传递给JS(如样式表ID、按钮ID)
    wp_localize_script(
        'daynight-switcher-script',
        'dns_data',
        array(
            'lightStyleId' => 'daynight-switcher-light-style-css',
            'darkStyleId' => 'daynight-switcher-dark-style-css',
            'buttonId'    => 'daynight-toggle',
      //     'textLight'   => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
      //        'textDark'    => esc_html__( '切换到白天模式', 'daynight-switcher' ),
        )
    );
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_styles_scripts' );

/**
 * 在页面<body>开头添加切换按钮(兼容WP5.2+)
 */

function dns_add_toggle_button() {
    if ( is_admin() ) return; // 排除后台
    ?>
    <button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
        <?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
    </button>
    <?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );

/**
 * 关键修复:提前在页面头部禁用非必要样式表,避免初始闪烁
 * 逻辑:根据本地存储或系统时间,立即禁用不需要的样式表
 */

function dns_early_disable_unnecessary_styles() {
    ?>
    <script>
        (function() {
            // 1. 获取用户偏好或系统默认模式
            const savedMode = localStorage.getItem('theme-mode');
            let initialMode;
           
            if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
                initialMode = savedMode;
            } else {
                const hour = new Date().getHours();
                initialMode = (hour >= 6 && hour < 18) ? 'light' : 'dark'; // 6-18点白天
            }

            // 2. 禁用不需要的样式表(避免页面初始渲染错误)
            const lightStyle = document.getElementById('<?php echo esc_js('daynight-switcher-light-style-css'); ?>');
            const darkStyle = document.getElementById('<?php echo esc_js('daynight-switcher-dark-style-css'); ?>');
           
            if (lightStyle && darkStyle) {
                if (initialMode === 'light') {
                    darkStyle.disabled = true; // 禁用暗夜样式
                } else {
                    lightStyle.disabled = true; // 禁用白天样式
                }
            }
        })();
    </script>
    <?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_early_disable_unnecessary_styles', 1 );
</head></body>

Js源码如下:

document.addEventListener('DOMContentLoaded', function() {
    // 检查本地化数据是否正常
    if (typeof dns_data === 'undefined') {
        console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
        return;
    }

    // 解构本地化数据
    const { lightStyleId, darkStyleId, buttonId, textLight, textDark } = dns_data;

    // 获取DOM元素
    const lightStyle = document.getElementById(lightStyleId);
    const darkStyle = document.getElementById(darkStyleId);
    const toggleButton = document.getElementById(buttonId);

    // 元素存在性检查
    if (!lightStyle || !darkStyle || !toggleButton) {
        console.error('日夜模式切换器:未找到核心元素,请检查样式表/按钮ID是否正确!');
        return;
    }

    console.log('日夜模式切换器:核心元素加载完成!');

    /**
     * 设置主题模式(更新样式、按钮文本、body类)
     * @param {string} mode - 'light'或'dark'
     * @param {boolean} save - 是否保存到localStorage
     */

    function setMode(mode, save = true) {
        // 更新样式表状态
        if (mode === 'light') {
            lightStyle.disabled = false;
            darkStyle.disabled = true;
            toggleButton.textContent = textLight;
            toggleButton.setAttribute('aria-pressed', 'false');
        } else {
            lightStyle.disabled = true;
            darkStyle.disabled = false;
            toggleButton.textContent = textDark;
            toggleButton.setAttribute('aria-pressed', 'true');
        }

        // 更新body类(方便扩展CSS)
        document.body.classList.remove('light-mode', 'dark-mode');
        document.body.classList.add(`${mode}-mode`);

        // 保存用户偏好(可选)
        if (save) localStorage.setItem('theme-mode', mode);
    }

    /**
     * 获取系统默认模式
     * @returns {string} 'light'或'dark'
     */

    function getSystemMode() {
        const hour = new Date().getHours();
        return (hour >= 6 && hour < 18) ? 'light' : 'dark';
    }

    // --- 初始化 ---
    const savedMode = localStorage.getItem('theme-mode');
    const initialMode = savedMode && (savedMode === 'light' || savedMode === 'dark')
        ? savedMode
        : getSystemMode();

    setMode(initialMode, false); // 初始化模式(不保存)
    console.log(`日夜模式切换器:初始模式为${initialMode}`);

    // --- 切换按钮点击事件 ---
    toggleButton.addEventListener('click', function() {
        const currentMode = document.body.classList.contains('light-mode') ? 'light' : 'dark';
        const newMode = currentMode === 'light' ? 'dark' : 'light';
        setMode(newMode); // 切换并保存偏好
        console.log(`日夜模式切换器:已切换至${newMode}`);
    });
});

如上方式实现了暗夜模式的识别和切换,根据用户端的时间,采用“用户偏好优先,无偏好时根据本地时间自动判断,并支持手动切换和保存偏好”的模式。设定6点-18点为白天模式,18-次日6点为暗夜模式。

这个版本有一个问题出现了。

初始页面加载时的优化 (防止闪烁 FOUC):

1.dns_early_disable_unnecessary_styles 这个PHP函数非常关键。它在页面标签中以最高优先级(wp_head, 1)执行一个小的JavaScript片段。

2.这个JS片段会立即检查 localStorage 或根据时间判断初始模式,然后直接禁用不需要的CSS文件(style-light.cssstyle-dark.css)。

3.这样做的好处是,在浏览器开始渲染页面之前,已经确定并加载了正确的样式,有效避免了页面先显示一种模式然后迅速切换到另一种模式的“闪烁”现象 (Flash Of Unstyled Content)。

页面前端依然很不给面子的闪烁,白天和暗夜模式底色反差太大,一闪一闪让人很不舒服,本想着退而求其次,将就就好,管它白天闪晚上,还是晚上闪白天。然后,这两三天来,白天都在看房,跟着中介看这套看那套的,折腾得累半死,回家洗完澡一躺就睡了,这个问题也就这样搁置了几天。晚上得闲,一打开页面,页面切换的时候,闪得我自己都很不舒服,算了算了,我还是再修一修吧。

出现这个闪烁的原因,很可能是因为浏览器在解析HTML并应用内联JS脚本禁用CSS之前,已经短暂地加载并应用了所有样式表。如果 style-dark.css 中的样式优先级更高,或者它定义的默认颜色在没有其他限制的情况下会覆盖 style-light.css,就会导致先显示深色。

解决这个问题的更健壮方法是利用 link 标签的 media 属性,它在CSS加载层面就阻止了不必要的样式表被应用,而不是在JS加载后才去禁用。

产生了第二版插件代码:

  1. wp_enqueue_style 中使用 media=”not all”:
    • 通过在PHP中将 style-dark.css 的 media 属性设置为 ‘not all’,我们告诉浏览器,这个样式表在任何媒体类型下都不适用。这意味着浏览器在解析HTML时,根本不会加载和应用 style-dark.css 的内容,从而避免了它在初始渲染时可能造成的深色闪烁。
    • style-light.css 仍然保持 media=”all”,确保默认的白天模式样式能被加载。
  2. 早期JS脚本中切换 media 属性:
    • 在 dns_early_disable_unnecessary_styles 中,我们不再使用 style.disabled = true/false,而是修改 style.media 属性。
    • link.media = ‘all’ 会让浏览器立即应用该样式表。
    • link.media = ‘not all’ 会让浏览器立即禁用该样式表。
    • 这种方法比 disabled 属性更可靠,因为它在CSS加载和应用机制的更底层进行控制,通常能更快、更彻底地防止不希望的样式被渲染。
  3. setMode 函数中切换 media 属性:
    • 手动切换模式时,JavaScript也需要同步地修改样式表的 media 属性,以确保页面动态地更新为正确的模式。

php代码如下:

<?php
/**
 * Plugin Name: 日夜模式切换器
 * Plugin URI: https://www.edzbe.com
 * Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
 * Version: 1.0.2
 * Author: 耳朵的主人
 * Author URI: https://www.edzbe.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: daynight-switcher
 * Domain Path: /languages
 */

// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * 注册并加载日夜模式样式和脚本
 */

function dns_enqueue_styles_scripts() {
    // 获取主题目录URI(确保CSS文件位于主题的/css/子目录)
    $theme_css_dir_uri = get_template_directory_uri() . '/css/';

    // 注册白天模式样式(默认 media="all",提供一个稳定的基线,避免布局错乱)
    wp_enqueue_style(
        'daynight-switcher-light-style',
        $theme_css_dir_uri . 'style-light.css',
        array(),
        '1.0.0',
        'all' // 白天模式默认加载,作为页面的基础颜色模式
    );

    // 注册暗夜模式样式(默认 media="not all",仅在需要时激活)
    wp_enqueue_style(
        'daynight-switcher-dark-style',
        $theme_css_dir_uri . 'style-dark.css',
        array(),
        '1.0.0',
        'not all' // 暗夜模式初始不加载,避免闪烁
    );

    // 注册切换脚本(在页脚加载,确保DOM就绪)
    wp_enqueue_script(
        'daynight-switcher-script',
        plugins_url( 'js/daynight-switcher.js', __FILE__ ),
        array(),
        '1.0.0',
        true
    );

    // 将PHP数据传递给JS(如样式表ID、按钮ID)
    wp_localize_script(
        'daynight-switcher-script',
        'dns_data',
        array(
            'lightStyleId' => 'daynight-switcher-light-style-css',
            'darkStyleId' => 'daynight-switcher-dark-style-css',
            'buttonId'    => 'daynight-toggle',
            'textLight'   => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
            'textDark'    => esc_html__( '切换到白天模式', 'daynight-switcher' ),
        )
    );
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_styles_scripts' );

/**
 * 在页面<body>开头添加切换按钮(兼容WP5.2+)
 */

function dns_add_toggle_button() {
    if ( is_admin() ) return; // 排除后台
    ?>
    <button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
        <?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
    </button>
    <?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );

/**
 * 关键修复:提前在页面头部设置正确的样式表和body类,避免初始闪烁
 * 逻辑:根据本地存储或系统时间,在初始加载时,如果需要暗色模式,则立即激活暗色模式并禁用白天模式。
 * 否则(即需要白天模式),白天模式已经默认加载,只需设置body类。
 */

function dns_early_disable_unnecessary_styles() {
    ?>
    <script>
        (function() {
            // 1. 获取用户偏好或系统默认模式
            const savedMode = localStorage.getItem('theme-mode');
            let initialMode;
           
            if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
                initialMode = savedMode;
            } else {
                const hour = new Date().getHours();
                initialMode = (hour >= 6 && hour < 18) ? 'light' : 'dark'; // 6-18点白天
            }

            // 2. 只有在初始模式是“dark”时才需要立即操作样式表,
            // 因为“light”模式的样式表已经默认加载 media="all"
            if (initialMode === 'dark') {
                const lightStyle = document.getElementById('<?php echo esc_js('daynight-switcher-light-style-css'); ?>');
                const darkStyle = document.getElementById('<?php echo esc_js('daynight-switcher-dark-style-css'); ?>');
               
                if (lightStyle && darkStyle) {
                    lightStyle.media = 'not all'; // 禁用白天样式
                    darkStyle.media = 'all';      // 激活暗夜样式
                }
            }
           
            // 3. 设置 body 的 class,确保全局背景色等样式同步
            // 这一步在任何模式下都执行,以确保 body 类始终是正确的
            document.body.classList.remove('light-mode', 'dark-mode'); // 移除可能的旧类
            document.body.classList.add(`${initialMode}-mode`); // 添加当前模式类

        })();
    </script>
    <?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_early_disable_unnecessary_styles', 1 );


</head></body>

js代码如下:

document.addEventListener('DOMContentLoaded', function() {
    // 检查本地化数据是否正常
    if (typeof dns_data === 'undefined') {
        console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
        return;
    }

    // 解构本地化数据
    const { lightStyleId, darkStyleId, buttonId, textLight, textDark } = dns_data;

    // 获取DOM元素
    const lightStyle = document.getElementById(lightStyleId);
    const darkStyle = document.getElementById(darkStyleId);
    const toggleButton = document.getElementById(buttonId);

    // 元素存在性检查
    if (!lightStyle || !darkStyle || !toggleButton) {
        console.error('日夜模式切换器:未找到核心元素,请检查样式表/按钮ID是否正确!');
        return;
    }

    console.log('日夜模式切换器:核心元素加载完成!');

    /**
     * 设置主题模式(更新样式、按钮文本、body类)
     * @param {string} mode - 'light'或'dark'
     * @param {boolean} save - 是否保存到localStorage
     */

    function setMode(mode, save = true) {
        // 更新样式表状态,通过修改 media 属性来激活/禁用样式
        if (mode === 'light') {
            lightStyle.media = 'all';     // 激活白天样式
            darkStyle.media = 'not all';  // 禁用暗夜样式
            toggleButton.textContent = textLight;
            toggleButton.setAttribute('aria-pressed', 'false');
        } else { // mode === 'dark'
            lightStyle.media = 'not all'; // 禁用白天样式
            darkStyle.media = 'all';      // 激活暗夜样式
            toggleButton.textContent = textDark;
            toggleButton.setAttribute('aria-pressed', 'true');
        }

        // 更新body类(方便扩展CSS)
        document.body.classList.remove('light-mode', 'dark-mode');
        document.body.classList.add(`${mode}-mode`);

        // 保存用户偏好(可选)
        if (save) localStorage.setItem('theme-mode', mode);
    }

    /**
     * 获取系统默认模式
     * @returns {string} 'light'或'dark'
     */

    function getSystemMode() {
        const hour = new Date().getHours();
        return (hour >= 6 && hour < 18) ? 'light' : 'dark';
    }

    // --- 初始化 ---
    // 页面加载时的初始化逻辑已经由PHP中的dns_early_disable_unnecessary_styles处理,
    // 这里再次调用setMode主要是为了同步按钮文本和body class,
    // 并且确保在JS完全加载后,如果有未被早期脚本处理的逻辑(虽然现在应该没有了),也能正确执行。
    const savedMode = localStorage.getItem('theme-mode');
    const initialMode = savedMode && (savedMode === 'light' || savedMode === 'dark')
        ? savedMode
        : getSystemMode();

    // 注意:这里的 setMode(initialMode, false) 仅仅是确保DOM加载完成后,按钮文本等细节能够正确初始化。
    // 真正避免闪烁的样式切换,由 dns_early_disable_unnecessary_styles 中的内联脚本处理。
    setMode(initialMode, false);
    console.log(`日夜模式切换器:初始模式为${initialMode}`);

    // --- 切换按钮点击事件 ---
    toggleButton.addEventListener('click', function() {
        const currentMode = document.body.classList.contains('light-mode') ? 'light' : 'dark';
        const newMode = currentMode === 'light' ? 'dark' : 'light';
        setMode(newMode); // 切换并保存偏好
        console.log(`日夜模式切换器:已切换至${newMode}`);
    });
});

 

如上代码,其实应该是正常解决方案的,就是创建的 style-light.css style-dark.css 文件如果只有配色方案,网页加载的时候,先加载 style.css 框架样式文件,这样页面就正常显示,只是配色根据选择加载,但是我又失败了,因为我开始弄的时候,把默认的style.css 框架样式直接套到 style-light.css style-dark.css 这两个文件里了,要修改,又要重新开始分离出配色单独创建样式表,不行,不行,这工作量太大了,我不要。

我给Gemini发送了我的想法解决这个问题: 先加载页面的判断设置,直接屏蔽不加载另一种模式。Gemini很给力的给出了解决方案。

只加载所需的模式样式,而不是先加载一个再切换,避免任何形式的“闪烁”或“布局混乱”。

我之前的方案试图通过 `media` 属性和内联 JS 来解决,但在某些浏览器或加载条件下,即使是极快的切换,也可能出现视觉上的延迟。实现“直接屏蔽不加载另一种模式”的最彻底方法是:在 PHP 生成 HTML 的 `<head>` 部分时,就根据用户的偏好或时间判断,只输出当前所需模式的 `<link>` 标签,而完全不输出另一个模式的 `<link>` 标签。

但这有个挑战:PHP 在服务器端运行,它无法直接读取用户浏览器中的 `localStorage`。它只能读取 `$_COOKIE`。所以,为了让 PHP 在第一次加载页面时就知道用户偏好,JavaScript 必须在每次模式切换时,除了更新 `localStorage`,还要同时更新一个 Cookie

最终版本php代码如下:

<?php
/**
 * Plugin Name: 日夜模式切换器
 * Plugin URI: https://www.edzbe.com
 * Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
 * Version: 1.0.3
 * Author: 耳朵的主人
 * Author URI: https://www.edzbe.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: daynight-switcher
 * Domain Path: /languages
 */

// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * 提前判断初始模式(PHP服务器端)
 * 优先级:Cookie (用户偏好) > 服务器时间
 * @return string 'light' 或 'dark'
 */

function dns_get_initial_mode_server_side() {
    // 1. 尝试从Cookie获取用户偏好
    if ( isset( $_COOKIE['theme-mode'] ) && in_array( $_COOKIE['theme-mode'], ['light', 'dark'] ) ) {
        return $_COOKIE['theme-mode'];
    }

    // 2. 如果Cookie不存在,根据服务器时间判断
    $hour = (int) current_time( 'H', true ); // 使用WordPress的 current_time 获取当前小时,避免时区问题
    return ($hour >= 6 && $hour < 18) ? 'light' : 'dark'; // 6-18点白天
}

/**
 * 注册并加载日夜模式脚本
 * 注意:日夜模式的样式文件不再在这里直接 enqueue,而是由 dns_output_conditional_styles 动态输出。
 */

function dns_enqueue_scripts() {
    // 获取主题目录URI
    $theme_css_dir_uri = get_template_directory_uri() . '/css/'; // 仍然传递给JS,以防需要手动构建 link 标签 fallback

    // 注册切换脚本(在页脚加载,确保DOM就绪)
    wp_enqueue_script(
        'daynight-switcher-script',
        plugins_url( 'js/daynight-switcher.js', __FILE__ ),
        array(),
        '1.0.0',
        true
    );

    // 将PHP数据传递给JS(如样式表ID、按钮ID、CSS文件路径)
    wp_localize_script(
        'daynight-switcher-script',
        'dns_data',
        array(
            'lightStyleId'      => 'daynight-switcher-light-style-css',
            'darkStyleId'       => 'daynight-switcher-dark-style-css',
            'buttonId'          => 'daynight-toggle',
            'textLight'         => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
            'textDark'          => esc_html__( '切换到白天模式', 'daynight-switcher' ),
            'lightStyleUrl'     => $theme_css_dir_uri . 'style-light.css', // 传递CSS路径给JS,用于动态创建link标签
            'darkStyleUrl'      => $theme_css_dir_uri . 'style-dark.css',  // 传递CSS路径给JS,用于动态创建link标签
        )
    );
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_scripts' ); // 重命名函数,因为不再enqueue styles

/**
 * 在页面<body>开头添加切换按钮(兼容WP5.2+)
 */

function dns_add_toggle_button() {
    if ( is_admin() ) return; // 排除后台
    ?>
    <button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
        <?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
    </button>
    <?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );

/**
 * 核心:在页面头部根据PHP判断动态输出正确的样式表和body类
 * 这将避免任何模式切换时的闪烁。
 */

function dns_output_conditional_styles() {
    if ( is_admin() ) return; // 排除后台

    $initial_mode = dns_get_initial_mode_server_side();
    $theme_css_dir_uri = get_template_directory_uri() . '/css/';
    $version = '1.0.0'; // 样式表的版本号

    // 输出正确的样式表
    if ( $initial_mode === 'light' ) {
        echo '<link rel="stylesheet" id="daynight-switcher-light-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-light.css?ver=' . $version ) . '" type="text/css" media="all"/>';
        echo '<link rel="stylesheet" id="daynight-switcher-dark-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-dark.css?ver=' . $version ) . '" type="text/css" media="not all"/>';
    } else { // dark mode
        echo '<link rel="stylesheet" id="daynight-switcher-light-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-light.css?ver=' . $version ) . '" type="text/css" media="not all"/>';
        echo '<link rel="stylesheet" id="daynight-switcher-dark-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-dark.css?ver=' . $version ) . '" type="text/css" media="all"/>';
    }

    // 立即在body标签上设置正确的class,防止任何延迟闪烁
    // 注意:这个内联脚本在<?php标签内,所以它会先于任何其他JS执行
    ?>
    <script>
        // 确保body class在HTML解析时就存在
        document.documentElement.classList.add('<?php echo esc_js( $initial_mode ); ?>-mode');
        document.body.classList.add('<?php echo esc_js( $initial_mode ); ?>-mode');

        // 后备或纠正逻辑(如果localStorage与PHP判断不符,或Cookie过期)
        (function() {
            const savedMode = localStorage.getItem('theme-mode');
            const currentInitialMode = '<?php echo esc_js( $initial_mode ); ?>'; // PHP决定的初始模式

            if (savedMode && savedMode !== currentInitialMode) {
                // 如果localStorage和PHP的初始判断不一致,JS会立即纠正
                const lightStyle = document.getElementById('daynight-switcher-light-style-css');
                const darkStyle = document.getElementById('daynight-switcher-dark-style-css');

                if (lightStyle && darkStyle) {
                    if (savedMode === 'light') {
                        lightStyle.media = 'all';
                        darkStyle.media = 'not all';
                    } else { // savedMode === 'dark'
                        lightStyle.media = 'not all';
                        darkStyle.media = 'all';
                    }
                }
                document.documentElement.classList.remove('light-mode', 'dark-mode');
                document.documentElement.classList.add(`${savedMode}-mode`);
                document.body.classList.remove('light-mode', 'dark-mode');
                document.body.classList.add(`${savedMode}-mode`);
            }
        })();
    </script>
    <?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_output_conditional_styles', 1 );


</head></body>

js代码如下:

document.addEventListener('DOMContentLoaded', function() {
    // 检查本地化数据是否正常
    if (typeof dns_data === 'undefined') {
        console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
        return;
    }

    // 解构本地化数据
    const { lightStyleId, darkStyleId, buttonId, textLight, textDark, lightStyleUrl, darkStyleUrl } = dns_data;

    // 获取DOM元素
    const lightStyle = document.getElementById(lightStyleId);
    const darkStyle = document.getElementById(darkStyleId);
    const toggleButton = document.getElementById(buttonId);

    // 确保样式表元素存在。如果PHP动态输出失败,这里可能是null。
    // 但是在最新的方案中,PHP一定会输出这两个link标签,只是media属性不同。
    if (!lightStyle || !darkStyle || !toggleButton) {
        // Fallback: 如果PHP没有输出link标签,JS动态创建它们
        console.warn('日夜模式切换器:未找到核心样式表元素。尝试动态创建。');
        // 如果lightStyle或darkStyle不存在,这里需要动态创建。
        // 为了简化,这里假设它们总是由PHP输出。如果真的未找到,需要更复杂的JS fallback。
        // 目前先跳过,因为PHP应该确保它们存在。
        // return; // 暂时不返回,让下面的逻辑有机会运行
    }


    console.log('日夜模式切换器:核心元素加载完成!');

    /**
     * 设置Cookie
     * @param {string} name - Cookie名称
     * @param {string} value - Cookie值
     * @param {number} days - Cookie过期天数
     */

    function setCookie(name, value, days) {
        let expires = "";
        if (days) {
            const date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = "; expires=" + date.toUTCString();
        }
        document.cookie = name + "=" + (value || "")  + expires + "; path=/";
    }

    /**
     * 设置主题模式(更新样式、按钮文本、body类、同步Cookie)
     * @param {string} mode - 'light'或'dark'
     * @param {boolean} save - 是否保存到localStorage和Cookie
     */

    function setMode(mode, save = true) {
        // 更新样式表状态,通过修改 media 属性来激活/禁用样式
        if (mode === 'light') {
            if (lightStyle) lightStyle.media = 'all';     // 激活白天样式
            if (darkStyle) darkStyle.media = 'not all';  // 禁用暗夜样式
            toggleButton.textContent = textLight;
            toggleButton.setAttribute('aria-pressed', 'false');
        } else { // mode === 'dark'
            if (lightStyle) lightStyle.media = 'not all'; // 禁用白天样式
            if (darkStyle) darkStyle.media = 'all';      // 激活暗夜样式
            toggleButton.textContent = textDark;
            toggleButton.setAttribute('aria-pressed', 'true');
        }

        // 更新body和html根元素的类(方便扩展CSS)
        document.documentElement.classList.remove('light-mode', 'dark-mode');
        document.documentElement.classList.add(`${mode}-mode`);
        document.body.classList.remove('light-mode', 'dark-mode');
        document.body.classList.add(`${mode}-mode`);

        // 保存用户偏好(可选)到 localStorage 和 Cookie
        if (save) {
            localStorage.setItem('theme-mode', mode);
            setCookie('theme-mode', mode, 30); // Cookie保存30天
        }
    }

    /**
     * 获取系统默认模式
     * @returns {string} 'light'或'dark'
     */

    function getSystemMode() {
        const hour = new Date().getHours();
        return (hour >= 6 && hour < 18) ? 'light' : 'dark';
    }

    // --- 初始化 ---
    // 页面加载时的初始模式判断和样式设置已由PHP在服务器端完成,
    // 这里的JS初始化主要是确保按钮文本正确,并处理LocalStorage和Cookie可能不同步的情况。
    const savedMode = localStorage.getItem('theme-mode');
    let initialMode;

    if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
        initialMode = savedMode;
    } else {
        initialMode = getSystemMode();
    }

    // 确保当前的Cookie也与initialMode一致,防止PHP下次加载时判断错误
    setCookie('theme-mode', initialMode, 30);

    // 调用 setMode 来同步按钮文本,以及确保 body class 即使在PHP未能设置时也能被设置(虽然PHP现在会设置)
    setMode(initialMode, false);
    console.log(`日夜模式切换器:初始模式为${initialMode}`);


    // --- 切换按钮点击事件 ---
    toggleButton.addEventListener('click', function() {
        // 从html根元素获取当前模式,因为CSS可能也通过html根元素控制
        const currentMode = document.documentElement.classList.contains('light-mode') ? 'light' : 'dark';
        const newMode = currentMode === 'light' ? 'dark' : 'light';
        setMode(newMode); // 切换并保存偏好
        console.log(`日夜模式切换器:已切换至${newMode}`);
    });
});

核心原理和解决闪烁的方法:

1. PHP 服务器端预判: PHP 在页面生成之初(在 `wp_head` 动作中,优先级为1),就通过 `dns_get_initial_mode_server_side()` 函数,根据 `theme-mode` Cookie 或服务器时间,判断出应该显示的模式。

2. 只输出所需 `<link>` 标签: `dns_output_conditional_styles()` 函数根据 PHP 的判断结果,直接在 HTML 的 `<head>` 中输出 两个 `<link>` 标签。
• 正确的模式 的 `<link>` 标签 `media` 属性是 `all`
• 不正确的模式 的 `<link>` 标签 `media` 属性是 `not all`
这样,在浏览器开始解析和渲染 HTML 时,它只会加载并应用正确的模式样式,从而从源头上避免了加载错误样式导致的闪烁。

3. `document.documentElement` `document.body` 类: PHP 还会输出一个内联 `<script>`,它会立即为 `<html>``<body>` 元素添加正确的模式类(例如 `dark-mode`)。这对于依赖这些类来设置背景色、字体颜色等全局样式的 CSS 规则至关重要,确保页面在渲染时就有正确的颜色基调。

4. JavaScript Cookie 同步: JS 代码在 `setMode` 时,除了更新 `localStorage`,还会更新一个 `theme-mode` Cookie。这样,用户下次访问时,PHP 就能从 Cookie 中读取到用户上次的选择。

5. 内联 JS 后备纠正: PHP 输出的内联 JS 中含有一个小的逻辑,用于比较 PHP 的判断(来自 Cookie 或时间)和 `localStorage` 中的用户偏好。如果两者不一致(例如,用户清除了 Cookie 但 `localStorage` 还在),这个内联 JS 会立即纠正模式,提供额外的鲁棒性。

代码仅供参考,在需要调用按钮的前端,添加按钮:

<button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
<?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
</button>

还有一个问题就是,header.php文件必须包含<?php wp_head(); ?>footer.php文件代码必须包含 <?php wp_footer(); ?> ,按钮在二者之间的位置插入,否则无法正常加载,为什么,我也不知道,我是一步一步修过来的,怎么修的,不知道。

顺便提供下我的按钮图标:

完整插件代码下载:daynight-switcher

伪技术贴完工,睡觉。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

听众留言

  1. 老麦说道:

    如果主题太久没折腾,积压很多插件或者修改后想要做好暗夜模式的适配是很麻烦,不过现在的AI这么强,提需求慢慢适配即可。

  2. 虫虫说道:

    我还用着iPhoneX,没打算换了。如果换也换国产机了。

    1. @虫虫 从iPhone3开始用的苹果,十几年已经习惯了操作系统,现在全家也都是苹果设备,隔空投送什么的都挺方便的。
      我也有几台iPhone X,感觉手感最好的就是它了,圆滑的边框。

  3. 江子渔说道:

    嘿嘿,我就觉得两套样式整太复杂了,就改几幅颜色的事儿,所以直接用两个样式类来切换算逑,然后读取localStorage完事。

    1. @江子渔 是的,理论上一样的,但是我主要是样式文件太杂乱,各种拼接的成份,单独在一个类里实现颜色的替换,还来改去故障太多,我就复制原来的样式,单独对应改颜色,这样偷懒来实现了。

  4. 段先森说道:

    代码没有夜间模式哦

    1. @段先森 哈哈,我不是完美主义者,我会说服我自己,这样就可以了。
      昨晚弄完就发现了代码框没有熬夜模式切换,一想到又要增加这个,我就好累,睡觉去了。

  5. acevs说道:

    还用11的路过,换手机总归是好事。感觉世界还是唯心的多点。

    1. @acevs 唯心唯物这个在我身上可能两方博弈吧,我自认为是个很理性客观的人,不过偶尔还是个人情绪化了。

  6. obaby说道:

    新的 iPhone 香吗?我还在用 13
    之前我用插件加过页面模式,效果一般,又给去掉了。夜间模式最大的问题是图片的处理很多都处理不好,切换到夜间模式,有的图片就成了闪光灯。有的图片就成了无底洞,😂

    1. @obaby 贴图确实这样,颜色各不同,有的色系深,有的色系浅,要么太闪眼,要么黑不隆咚的,不过我有个大胆的想法,如果这样,统一给img标签添加一个黑灰色的蒙层,会不会均衡一些。
      不香,一点都不香,屏幕太大,右手小拇指顶着单手操作,手指末节都压出一个坑了,而且后置摄像头全段式凸起,我的游戏手柄装不下去了,和平精英都打不了一点。

  7. 瓦匠说道:

    完美